In [1]:
import os
import pandas as pd
import plotly.express as px
import matplotlib.pyplot as plt
import seaborn as sns
from sqlalchemy import create_engine
from dotenv import load_dotenv
In [2]:
# === Chargement des variables d'environnement ===
load_dotenv()
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
In [3]:
# === Connexion à la base PostgreSQL RDS ===
try:
engine = create_engine(f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}")
df = pd.read_sql("SELECT * FROM jobs", con=engine)
print("✅ Données chargées depuis RDS avec succès")
except Exception as e:
print("❌ Erreur de connexion à la base RDS :", e)
df = pd.DataFrame() # On crée un df vide pour éviter de planter la suite
✅ Données chargées depuis RDS avec succès
In [4]:
# Nettoyage de base
if not df.empty:
df.dropna(subset=["title", "company", "location"], inplace=True)
df["title"] = df["title"].str.lower().str.strip()
df["company"] = df["company"].str.strip()
df["location"] = df["location"].str.strip()
# Aperçu des données
print("\nAperçu des données :")
print(df.head())
Aperçu des données :
id title company \
0 1 staff data scientist featured Mozilla Remote-first
1 2 associate cloud data scientist (m/f/d) TD SYNNEX
2 3 (usa) principal, data scientist Walmart
3 4 principal, data scientist – converse Walmart
4 5 data scientist Sun Life
location salary_min salary_max \
0 Remote Canada\nRemote Canada\nFull Time\nSenio... NaN NaN
1 Barcelona, Spain\nBarcelona, Spain\nFull Time\... NaN NaN
2 (USA) Bentonville Global Tech …\n(USA) Bentonv... NaN NaN
3 (USA) SUNNYVALE IV- 680 …\n(USA) SUNNYVALE IV-... NaN NaN
4 Sun Life Global Solutions …\nSun Life Global S... NaN NaN
contract_type source
0 None AI-jobs.net
1 None AI-jobs.net
2 None AI-jobs.net
3 None AI-jobs.net
4 None AI-jobs.net
In [7]:
# === Visualisation 1 : Offres par ville ===
df_city = df["location"].value_counts().head(10).reset_index()
df_city.columns = ["city", "count"]
fig1 = px.bar(df_city, x="city", y="count", title="Top 10 des villes avec le plus d'offres")
fig1.show()
In [8]:
# === Visualisation 2 : Entreprises qui recrutent le plus ===
df_company = df["company"].value_counts().head(10).reset_index()
df_company.columns = ["company", "count"]
fig2 = px.pie(df_company, names="company", values="count", title="Top entreprises qui recrutent")
fig2.show()
In [9]:
# === Visualisation 3 : Distribution des salaires ===
df_salaire = df.dropna(subset=["salary_min", "salary_max"])
fig3 = px.box(df_salaire, y="salary_max", points="all", title="Distribution des salaires max")
fig3.show()
In [11]:
# === Visualisation 4 : Offres par source ===
df_source = df["source"].value_counts().reset_index()
df_source.columns = ["source", "count"]
fig4 = px.pie(df_source, names="source", values="count", title="Origine des offres (API vs Scraping)")
fig4.show()
In [12]:
# === Visualisation 5 : Top intitulés de poste ===
df_title = df["title"].value_counts().head(10).reset_index()
df_title.columns = ["title", "count"]
fig5 = px.bar(df_title, x="title", y="count", title="Top intitulés de poste")
fig5.show()
In [14]:
# === Visualisation 7 : Nombre d'offres avec ou sans salaire ===
df["has_salary"] = df["salary_min"].notnull() & df["salary_max"].notnull()
salary_counts = df["has_salary"].value_counts().rename({True: "Avec salaire", False: "Sans salaire"})
fig7 = px.pie(values=salary_counts.values, names=salary_counts.index, title="Présence des salaires dans les offres")
fig7.show()
In [26]:
# === Insight exemple ===
print("\n🔎 Insight :")
print("La majorité des offres viennent de :", df["source"].value_counts().idxmax())
print("Nombre d'offres avec salaire renseigné :", len(df_salaire))
🔎 Insight : La majorité des offres viennent de : AI-jobs.net Nombre d'offres avec salaire renseigné : 4
In [30]:
# === Insights supplémentaires ===
print("\n🔎 Insights supplémentaires :")
print("1. Nombre total d'offres :", len(df))
print("2. Nombre d'entreprises uniques :", df["company"].nunique())
print("3. Nombre de villes uniques :", df["location"].nunique())
print("4. Intitulé de poste le plus fréquent :", df["title"].mode()[0])
print("5. Entreprise ayant posté le plus d'offres :", df["company"].value_counts().idxmax())
print("6. Salaire moyen max :", df["salary_max"].dropna().mean())
print("7. Salaire médian min :", df["salary_min"].dropna().median())
print("8. Pourcentage d'offres avec salaire renseigné :", round(df["has_salary"].mean() * 100, 2), "%")
print("9. Nombre d'intitulés de poste uniques :", df["title"].nunique())
print("10. Répartition API vs Scraping :")
print(df["source"].value_counts(normalize=True) * 100)
🔎 Insights supplémentaires : 1. Nombre total d'offres : 100 2. Nombre d'entreprises uniques : 71 3. Nombre de villes uniques : 89 4. Intitulé de poste le plus fréquent : formation data science 5. Entreprise ayant posté le plus d'offres : DATASCIENTEST 6. Salaire moyen max : 1802.0 7. Salaire médian min : 486.0 8. Pourcentage d'offres avec salaire renseigné : 4.0 % 9. Nombre d'intitulés de poste uniques : 71 10. Répartition API vs Scraping : source AI-jobs.net 50.0 Adzuna API 50.0 Name: proportion, dtype: float64
In [ ]: